package com.example.snakefinal;

import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.os.Build;

import androidx.core.app.ActivityCompat;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;

public class AudioRecordStart extends WebSocketListener {

    private AudioRecord audioRecord;
    // 采样率，现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率（22050, 16000, 11025）在一些设备上也可以使用。
    public static final int SAMPLE_RATE_INHZ = 16000;
    // 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。
    public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_DEFAULT;
    // 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
    public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
    private final Context applicationContext;
    private final int audioSource;
    // 帧大小，1280的整数倍
    private static final int FRAME_SIZE = 1280;
    private Thread thread;
    private WebSocket webSocket;
    private static final String hostUrl = "https://iat-api.xfyun.cn/v2/iat"; //中英文，http url 不支持解析 ws/wss schema
    private static final String appid = "5a0abe25"; //在控制台-我的应用获取
    private static final String apiSecret = "NDM5MTdkMjNlODcyZTY1M2RmYjc4MzE5"; //在控制台-我的应用-语音听写（流式版）获取
    private static final String apiKey = "2783d3c45deea4a1eedee49f8f22a216"; //在控制台-我的应用-语音听写（流式版）获取
    public static final int StatusFirstFrame = 0;
    public static final int StatusContinueFrame = 1;
    public static final int StatusLastFrame = 2;
    public static final Gson json = new Gson();
    Decoder decoder = new Decoder();
    private Snake snake;
    private volatile AtomicInteger state = new AtomicInteger(-1);

    public AudioRecordStart(Context applicationContext, int audioSource, Snake snake) {
        this.applicationContext = applicationContext;
        this.audioSource = audioSource;
        this.snake = snake;
    }

    public boolean isStart() {
        if (state.get() == -1) {
            return false;
        }
        return true;
    }

    public void start() {
        try {
            state.set(0);
            System.out.println("start");
            String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);
            OkHttpClient client = new OkHttpClient.Builder().build();
            String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
            Request request = new Request.Builder().url(url).build();
            webSocket = client.newWebSocket(request, this);
            System.out.println("end");
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    public void end() {
        state.set(2);
    }

    public void die() {
        audioRecord.stop();
        audioRecord.release();
        thread.interrupt();
        webSocket.close(1000, "");
        state.set(-1);
    }


    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        super.onOpen(webSocket, response);
        if (ActivityCompat.checkSelfPermission(applicationContext, android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            System.out.println("权限不足。");
            return;
        }
        audioRecord = new AudioRecord(audioSource, SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT, FRAME_SIZE);
        byte[] byteBuffer = new byte[FRAME_SIZE];
        // 开始采集语音信息。
        thread = new Thread(() -> {
            while (true) {
                int state = this.state.get();
                int read = audioRecord.read(byteBuffer, 0, FRAME_SIZE);
                if (read < 0) {
                    continue;
                }
                sendFrameData(webSocket, state, byteBuffer, read);
                if (state == 0) {
                    this.state.set(1);
                }
                if (state == 2) {
                    sendFrameData(webSocket, state, byteBuffer, read);
                    System.out.println("线程已结束");
                    break;
                }
                // 停止
                if (thread.isInterrupted()) {
                    System.out.println("停止服务。");
                    break;
                }
                try {
                    Thread.sleep(40);
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        thread.start();
        audioRecord.startRecording();
    }

    private static void sendFrameData(WebSocket webSocket, int status, byte[] buffer, int len) {
        System.out.println("send:" + status + "," + len);
        switch (status) {
            case StatusFirstFrame:   // 第一帧音频status = 0
                JsonObject frame = new JsonObject();
                JsonObject business = new JsonObject();  //第一帧必须发送
                JsonObject common = new JsonObject();  //第一帧必须发送
                JsonObject data = new JsonObject();  //每一帧都要发送
                // 填充common
                common.addProperty("app_id", appid);
                //填充business
                business.addProperty("language", "zh_cn");
                business.addProperty("domain", "iat");
                business.addProperty("accent", "mandarin");//中文方言请在控制台添加试用，添加后即展示相应参数值
                business.addProperty("dwa", "wpgs");//动态修正(若未授权不生效，在控制台可免费开通)
                //填充data
                data.addProperty("status", StatusFirstFrame);
                data.addProperty("format", "audio/L16;rate=16000");
                data.addProperty("encoding", "raw");
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    data.addProperty("audio", Base64.getEncoder().encodeToString(Arrays.copyOf(buffer, len)));
                }
                //填充frame
                frame.add("common", common);
                frame.add("business", business);
                frame.add("data", data);
                webSocket.send(frame.toString());
                break;
            case StatusContinueFrame:  //中间帧status = 1
                JsonObject frame1 = new JsonObject();
                JsonObject data1 = new JsonObject();
                data1.addProperty("status", StatusContinueFrame);
                data1.addProperty("format", "audio/L16;rate=16000");
                data1.addProperty("encoding", "raw");
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    data1.addProperty("audio", Base64.getEncoder().encodeToString(Arrays.copyOf(buffer, len)));
                }
                frame1.add("data", data1);
                webSocket.send(frame1.toString());
                break;
            case StatusLastFrame:    // 最后一帧音频status = 2 ，标志音频发送结束
                JsonObject frame2 = new JsonObject();
                JsonObject data2 = new JsonObject();
                data2.addProperty("status", StatusLastFrame);
                data2.addProperty("audio", "");
                data2.addProperty("format", "audio/L16;rate=16000");
                data2.addProperty("encoding", "raw");
                frame2.add("data", data2);
                webSocket.send(frame2.toString());
                System.out.println("sendlast");
                break;
        }
    }

    @Override
    public void onMessage(WebSocket webSocket, String text) {
        super.onMessage(webSocket, text);
        System.out.println(text);
        ResponseData resp = json.fromJson(text, ResponseData.class);
        if (resp != null) {
            if (resp.getCode() != 0) {
                System.out.println("code=>" + resp.getCode() + " error=>" + resp.getMessage() + " sid=" + resp.getSid());
                System.out.println("错误码查询链接：https://www.xfyun.cn/document/error-code");
                return;
            }
            if (resp.getData() != null) {
                if (resp.getData().getResult() != null) {
                    Text te = resp.getData().getResult().getText();
                    try {
                        decoder.decode(te);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(resp);
                if (resp.getData().getStatus() == 2) {
                    System.out.println("最终识别结果 ==》" + decoder.toString());
                    if (decoder.toString().contains("向上")) {
                        snake.switchHeading(Snake.Heading.UP);
                    }
                    if (decoder.toString().contains("向左")) {
                        snake.switchHeading(Snake.Heading.LEFT);
                    }
                    if (decoder.toString().contains("向右")) {
                        snake.switchHeading(Snake.Heading.RIGHT);
                    }
                    if (decoder.toString().contains("向下")) {
                        snake.switchHeading(Snake.Heading.DOWN);
                    }

                    die();
                }
            }
        }
    }

    @Override
    public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        super.onFailure(webSocket, t, response);
        try {
            if (null != response) {
                int code = response.code();
                System.out.println("onFailure code:" + code);
                System.out.println("onFailure body:" + response.body().string());
                if (101 != code) {
                    System.out.println("connection failed");
                    System.exit(0);
                }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
        URL url = new URL(hostUrl);
        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = format.format(new Date());
        StringBuilder builder = new StringBuilder("host: ").append(url.getHost()).append("\n").//
                append("date: ").append(date).append("\n").//
                append("GET ").append(url.getPath()).append(" HTTP/1.1");
        //System.out.println(builder);
        Charset charset = Charset.forName("UTF-8");
        Mac mac = Mac.getInstance("hmacsha256");
        SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(charset), "hmacsha256");
        mac.init(spec);
        byte[] hexDigits = mac.doFinal(builder.toString().getBytes(charset));
        String sha = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            sha = Base64.getEncoder().encodeToString(hexDigits);
        }

        //System.out.println(sha);
        String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);
        //System.out.println(authorization);
        HttpUrl httpUrl = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            httpUrl = HttpUrl.parse("https://" + url.getHost() + url.getPath()).newBuilder().//
                    addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(charset))).//
                    addQueryParameter("date", date).//
                    addQueryParameter("host", url.getHost()).//
                    build();
        }
        return httpUrl.toString();
    }

    public static class ResponseData {
        private int code;
        private String message;
        private String sid;
        private Data data;

        public int getCode() {
            return code;
        }

        public String getMessage() {
            return this.message;
        }

        public String getSid() {
            return sid;
        }

        public Data getData() {
            return data;
        }

        @Override
        public String toString() {
            return "ResponseData{" + "code=" + code + ", message='" + message + '\'' + ", sid='" + sid + '\'' + ", data=" + data + '}';
        }
    }

    public static class Data {
        private int status;
        private Result result;

        public int getStatus() {
            return status;
        }

        public Result getResult() {
            return result;
        }
    }

    public static class Result {
        int bg;
        int ed;
        String pgs;
        int[] rg;
        int sn;
        Ws[] ws;
        boolean ls;
        JsonObject vad;

        public Text getText() {
            Text text = new Text();
            StringBuilder sb = new StringBuilder();
            for (Ws ws : this.ws) {
                sb.append(ws.cw[0].w);
            }
            text.sn = this.sn;
            text.text = sb.toString();
            text.sn = this.sn;
            text.rg = this.rg;
            text.pgs = this.pgs;
            text.bg = this.bg;
            text.ed = this.ed;
            text.ls = this.ls;
            text.vad = this.vad == null ? null : this.vad;
            return text;
        }
    }

    public static class Ws {
        Cw[] cw;
        int bg;
        int ed;
    }

    public static class Cw {
        int sc;
        String w;
    }

    public static class Text {
        int sn;
        int bg;
        int ed;
        String text;
        String pgs;
        int[] rg;
        boolean deleted;
        boolean ls;
        JsonObject vad;

        @Override
        public String toString() {
            return "Text{" + "bg=" + bg + ", ed=" + ed + ", ls=" + ls + ", sn=" + sn + ", text='" + text + '\'' + ", pgs=" + pgs + ", rg=" + Arrays.toString(rg) + ", deleted=" + deleted + ", vad=" + (vad == null ? "null" : vad.getAsJsonArray("ws").toString()) + '}';
        }
    }

    //解析返回数据，仅供参考
    public static class Decoder {
        private Text[] texts;
        private int defc = 10;

        public Decoder() {
            this.texts = new Text[this.defc];
        }

        public synchronized void decode(Text text) {
            if (text.sn >= this.defc) {
                this.resize();
            }
            if ("rpl".equals(text.pgs)) {
                for (int i = text.rg[0]; i <= text.rg[1]; i++) {
                    this.texts[i].deleted = true;
                }
            }
            this.texts[text.sn] = text;
        }

        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (Text t : this.texts) {
                if (t != null && !t.deleted) {
                    sb.append(t.text);
                }
            }
            return sb.toString();
        }

        public void resize() {
            int oc = this.defc;
            this.defc <<= 1;
            Text[] old = this.texts;
            this.texts = new Text[this.defc];
            for (int i = 0; i < oc; i++) {
                this.texts[i] = old[i];
            }
        }

        public void discard() {
            for (int i = 0; i < this.texts.length; i++) {
                this.texts[i] = null;
            }
        }
    }

}
